//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using EffiProz.Core.Lib;




namespace EffiProz.Core
{
    /**
     * This class is used for grouping select results, especially for select
     * statements that include group by clause and nested aggregate functions.
     * It is used by the <b>Select</b> class regardless the existence of group by
     * clause.
     * <p>
     * When a group by clause is defined, a <b>ResultGroup</b> is used to hold
     * all column values and <b>AggregatingValue</b>s for each group.  When a group
     * by clause is not defined, one <b>ResultGroup</b> is used to hold all the
     * results.<p>
     *
     * All <b>ResultGroup</b>s are placed _in a <b>HashSet</b>.  Adding a new row
     * will first retrieve the corresponding group from the table, based on the
     * values _in the group by columns.  If a group is found, then the row
     * associated with the group will be returned.  Otherwise a new group is
     * created with the new row, and the new row is returned.
     * <p>
     * The <b>Select</b> can then update the values and <b>AggregatingValue</b>s
     * _in the returned row, rather than the original row.  This approach enables
     * nested aggregate functions, such as "count(id)+2, 20-count(id),
     * max(id)-min(id)" support.
     *
     * @author  Tony Lai
     * @version   1.7.2
     * @since   1.7.2
     * @see     Expression
     * @see     Select
     */

    // fredt@users - patch 1.7.2 - mods to use new HashSet class and to separate addRow and getRow operations
    public class GroupedResult
    {

        /** @todo fredt - initialise results on first use */
        private Result result;
        public int groupBegin;
        public int groupEnd;
        private bool isGrouped;
        private bool isAggregated;
        private Dictionary<int, ResultGroup> groups;
        private ResultGroup currGroup;

        public GroupedResult(Select select, Result.ResultMetaData meta)
        {

            result = new Result(meta);
            groupBegin = select.iResultLen;
            groupEnd = groupBegin + select.iGroupLen;
            isGrouped = groupBegin != groupEnd;
            isAggregated = select.isAggregated;

            if (isGrouped)
            {
                groups = new Dictionary<int, ResultGroup>();
            }
        }

        public Object[] getRow(Object[] row)
        {

            if (isGrouped)
            {
                ResultGroup newGroup = new ResultGroup(this, row);
                ResultGroup group = null;

                if (groups.TryGetValue(newGroup.GetHashCode(), out group))
                {
                    Array.Copy(group.row, row, row.Length);
                }
            }
            else if (isAggregated)
            {
                if (currGroup != null)
                {
                    Array.Copy(currGroup.row, row, row.Length);
                }
            }

            return row;
        }

        public void addRow(Object[] row)
        {

            if (isGrouped)
            {
                ResultGroup newGroup = new ResultGroup(this, row);

                currGroup = null;

                if (!groups.TryGetValue(newGroup.GetHashCode(), out currGroup))
                {
                    currGroup = newGroup;
                    groups.Add(currGroup.GetHashCode(), currGroup);
                    result.add(row);
                }
                else
                {
                    Array.Copy(row, 0, currGroup.row, 0, row.Length);
                }
            }
            else if (isAggregated)
            {
                if (currGroup == null)
                {
                    currGroup = new ResultGroup(this, row);

                    result.add(row);
                }
                else
                {
                    Array.Copy(row, 0, currGroup.row, 0, row.Length);
                }
            }
            else
            {
                result.add(row);
            }
        }

        public int size()
        {
            return result.getSize();
        }

        public Iterator iterator()
        {
            return result.iterator();
        }

        public Result getResult()
        {
            return result;
        }

        public class ResultGroup
        {

            public Object[] row;
            public int _hashCode;
            private GroupedResult _o = null;

            public ResultGroup(GroupedResult _o, Object[] row)
            {
                this._o = _o;
                this.row = row;
                _hashCode = 0;

                for (int i = _o.groupBegin; i < _o.groupEnd; i++)
                {
                    if (row[i] != null)
                    {
                        _hashCode += row[i].GetHashCode();
                    }
                }
            }

            public override int GetHashCode()
            {
                return _hashCode;
            }

            public override bool Equals(Object obj)
            {

                if (obj == this)
                {
                    return true;
                }

                if (obj == null || !(obj is ResultGroup))
                {
                    return false;
                }

                ResultGroup group = (ResultGroup)obj;

                for (int i = _o.groupBegin; i < _o.groupEnd; i++)
                {
                    if (!Equals(row[i], group.row[i]))
                    {
                        return false;
                    }
                }

                return true;
            }

            public new bool Equals(Object o1, Object o2)
            {
                return (o1 == null) ? o2 == null
                                    : o1.Equals(o2);
            }
        }
    }
}
